﻿using OpenNETCF;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading;

namespace OpenNETCF.Extensions.Unit.Test
{
    [TestClass()]
    public class CircularBufferTest
    {
        public TestContext TestContext { get; set; }

        [TestMethod()]
        public void EnqueueTest()
        {
            var buffer = new CircularBuffer<int>(3);

            var c = buffer.Count;

            buffer.Enqueue(1);
            Assert.AreEqual(1, buffer.Count);

            var p = buffer.Peek();
            Assert.AreEqual(1, p);

            buffer.Enqueue(2);
            Assert.AreEqual(2, buffer.Count);

            p = buffer.Peek();
            Assert.AreEqual(1, p);

            buffer.Enqueue(3);
            Assert.AreEqual(3, buffer.Count);
            Assert.IsTrue(buffer.IsFull);

            p = buffer.Peek();
            Assert.AreEqual(1, p);

            p = buffer.Dequeue();
            Assert.AreEqual(1, p);
            Assert.AreEqual(2, buffer.Count);
            Assert.IsFalse(buffer.IsFull);
            p = buffer.Peek();
            Assert.AreEqual(2, p);

            buffer.Enqueue(4);
            Assert.AreEqual(3, buffer.Count);
            Assert.IsTrue(buffer.IsFull);
            Assert.IsFalse(buffer.HasOverrun);

            p = buffer.Peek();
            Assert.AreEqual(2, p);

            AutoResetEvent are = new AutoResetEvent(false);
            buffer.Overrun += delegate
            {
                are.Set();
            };

            buffer.Enqueue(5);
            Assert.IsTrue(buffer.IsFull);
            Assert.IsTrue(buffer.HasOverrun);
            Assert.IsTrue(are.WaitOne(1000));

            are.Reset();

            p = buffer.Peek();
            Assert.AreEqual(3, p);
        }

        [TestMethod()]
        public void HighWaterTest()
        {
            var buffer = new CircularBuffer<int>(5);
            buffer.HighWaterLevel = 4;
            AutoResetEvent are = new AutoResetEvent(false);
            buffer.HighWater += delegate
            {
                are.Set();
            };

            buffer.Enqueue(1);
            Assert.IsFalse(are.WaitOne(0, false));
            buffer.Enqueue(2);
            Assert.IsFalse(are.WaitOne(0, false));
            buffer.Enqueue(3);
            Assert.IsFalse(are.WaitOne(0, false));

            // this should fire the event
            buffer.Enqueue(4);
            Assert.IsTrue(are.WaitOne(0, false));

            are.Reset();
            // already above, this should not reset
            buffer.Enqueue(5);
            Assert.IsFalse(are.WaitOne(0, false));

            buffer.Dequeue();
            buffer.Dequeue();

            // this should fire the again
            buffer.Enqueue(6);
            Assert.IsTrue(are.WaitOne(0, false));
        }

        [TestMethod()]
        public void LowWaterTest()
        {
            var buffer = new CircularBuffer<int>(5);
            buffer.LowWaterLevel = 2;
            AutoResetEvent are = new AutoResetEvent(false);
            buffer.LowWater += delegate
            {
                are.Set();
            };

            buffer.Enqueue(1);
            Assert.IsFalse(are.WaitOne(0, false));
            buffer.Enqueue(2);
            Assert.IsFalse(are.WaitOne(0, false));
            buffer.Enqueue(3);
            Assert.IsFalse(are.WaitOne(0, false));

            // this should fire the event
            buffer.Dequeue();
            Assert.IsTrue(are.WaitOne(0, false));

            are.Reset();
            // already below, this should not reset
            buffer.Dequeue();
            Assert.IsFalse(are.WaitOne(0, false));

            buffer.Enqueue(4);
            buffer.Enqueue(5);

            // this should fire the event again
            buffer.Dequeue();
            Assert.IsTrue(are.WaitOne(0, false));
        }

        [TestMethod()]
        public void LowWaterBoundsTest()
        {
            Exception ex = null;

            var buffer = new CircularBuffer<int>(5);

            try
            {
                buffer.LowWaterLevel = -1;
            }
            catch (Exception e)
            {
                ex = e;
            }

            Assert.IsNotNull(ex);
            ex = null;

            try
            {
                buffer.LowWaterLevel = 6;
            }
            catch (Exception e)
            {
                ex = e;
            }

            Assert.IsNotNull(ex);
            ex = null;

            buffer.LowWaterLevel = 0;
            buffer.LowWaterLevel = 5;
        }

        [TestMethod()]
        public void HighWaterBoundsTest()
        {
            Exception ex = null;

            var buffer = new CircularBuffer<int>(5);

            try
            {
                buffer.HighWaterLevel = -1;
            }
            catch (Exception e)
            {
                ex = e;
            }

            Assert.IsNotNull(ex);
            ex = null;

            try
            {
                buffer.HighWaterLevel = 6;
            }
            catch (Exception e)
            {
                ex = e;
            }

            Assert.IsNotNull(ex);
            ex = null;

            buffer.HighWaterLevel = 0;
            buffer.HighWaterLevel = 5;
        }

        private class TestItem<T> : IEquatable<TestItem<T>>
        {
            private static int m_index = 0;

            public TestItem()
            {
                Index = m_index++;
            }

            public int Index { get; private set; }

            public T Value { get; set; }

            public bool Equals(TestItem<T> other)
            {
                return this.Index == other.Index;
            }
        }

        [TestMethod()]
        public void FirstLastTest()
        {
            var buffer = new CircularBuffer<TestItem<int>>(20);

            var item1 = new TestItem<int> { Value = 1 };
            var item2 = new TestItem<int> { Value = 2 };
            var item3 = new TestItem<int> { Value = 3 };
            var item4 = new TestItem<int> { Value = 1 };

            buffer.Enqueue(item1);
            buffer.Enqueue(item2);
            buffer.Enqueue(item3);
            buffer.Enqueue(item4);

            var i = buffer.First(s => s.Value == 0);
            Assert.IsNull(i);
            i = buffer.Last(s => s.Value == 0);
            Assert.IsNull(i);
            i = buffer.First(s => s.Value == 1);
            Assert.AreEqual(item1, i);
            i = buffer.Last(s => s.Value == 1);
            Assert.AreEqual(item4, i);

            i = buffer.First(s => s.Value == 2);
            Assert.AreEqual(item2, i);
            var i2 = buffer.Last(s => s.Value == 2);
            Assert.AreEqual(i, i2);
        }

        [TestMethod()]
        public void DequeueMultipleTest()
        {
            var buffer = new CircularBuffer<TestItem<int>>(20);

            var item1 = new TestItem<int> { Value = 1 };
            var item2 = new TestItem<int> { Value = 2 };
            var item3 = new TestItem<int> { Value = 3 };
            var item4 = new TestItem<int> { Value = 1 };

            buffer.Enqueue(item1);
            buffer.Enqueue(item2);
            buffer.Enqueue(item3);
            buffer.Enqueue(item4);

            var p = buffer.Dequeue(2);
            Assert.AreEqual(2, p.Length);
            Assert.AreEqual(item1, p[0]);
            Assert.AreEqual(item2, p[1]);
            p = buffer.Dequeue(5);
            Assert.AreEqual(0, p.Length);
        }

        [TestMethod()]
        public void EnumerationTest()
        {
            var buffer = new CircularBuffer<TestItem<int>>(5);

            foreach (var i in buffer)
            {
            }

            var item1 = new TestItem<int> { Value = 1 };
            var item2 = new TestItem<int> { Value = 2 };
            var item3 = new TestItem<int> { Value = 3 };
            var item4 = new TestItem<int> { Value = 4 };

            buffer.Enqueue(item1);
            buffer.Enqueue(item2);
            buffer.Enqueue(item3);
            buffer.Enqueue(item4);

            var val = 1;

            foreach (var i in buffer)
            {
                Assert.AreEqual(val, i.Value);
                val++;
            }

            buffer.Dequeue();
            buffer.Dequeue();

            buffer.Enqueue(new TestItem<int> { Value = 5 });
            buffer.Enqueue(new TestItem<int> { Value = 6 });
            buffer.Enqueue(new TestItem<int> { Value = 7 });

            val = 3;

            foreach (var i in buffer)
            {
                Assert.AreEqual(val, i.Value);
                val++;
            }
        }
    }
}
